今天的主題是 TypedSQL 是 primsa 最今提供的功能,他可以自動幫你 auto generate 你的 SQL file 成一個 function 讓你可以在 prisma client 中使用它,同時擁有完好的 type safe 可以用於需要用 arguments 執行 SQL 指令,那我們廢話不多說開始今天的內容。
以下是今天的 model 來當作我們今天 demo 的資料,簡單介紹一下:
User 有多個 post 內容User 有多個追蹤的 trackingEvents
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
age Int
posts Post[]
trackingEvents TrackingEvent[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id],onDelete: Cascade)
authorId Int
createAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model TrackingEvent {
id Int @id @default(autoincrement())
type String
variant String
user User @relation(fields: [userId], references: [id],onDelete: Cascade)
userId Int
createAt DateTime @default(now())
}
接著我們寫一個 main function 當作我們 seed data 的 root cause ,然後 create user 搭配 faker 產生假資料,然後使用 Prisma.validator 確保 input 的 type safe ,同時你會發現有一個特別的參數 skipDuplicates 這是 prisma 對於有 unique 的欄位用來減少資料重複的 argument ,確保資料一制性
// prisma/seed.ts
import { faker } from '@faker-js/faker'
const main = async () => {
await prisma.user.deleteMany()
const usersInput = Prisma.validator(
prisma,
'user',
'createMany'
)({
data: new Array(NUM_USERS).fill('_').map(() => ({
email: faker.internet.email(),
age: faker.helpers.rangeToNumber({ min: 0, max: 99 }),
name: faker.person.fullName()
})),
skipDuplicates: true
})
const users = await prisma.user.createManyAndReturn(usersInput)
}
這邊簡單舉個例子,在用 createMany 的時候難免會有重複的 email 出現,但因為我們的 email 他是 unique 的所以會導致無法 create data ,所以我們需要加上 skipDuplicates:true 讓 prisma client 幫我們移除重複的 email 資料
const createMany = await prisma.user.createMany({
data: [
{ name: 'Bob', email: 'bob@prisma.io' },
{ name: 'Bobo', email: 'bob@prisma.io' }, // Duplicate unique key!
{ name: 'Yewande', email: 'yewande@prisma.io' },
{ name: 'Angelique', email: 'angelique@prisma.io' },
],
skipDuplicates: true, // Skip 'Bobo'
})
所以最終只會新增三筆
{
count: 3
}
接著我們加上 createFunnel 幫我們關聯 posts data 到 user
enum ExperimentVariant {
BlueBuyButton = "BlueBuyButton",
GreenBuyButton = "GreenBuyButton",
}
const main = async () => {
//..
await createFunnel(users.slice(0, COUNT_BLUE), ExperimentVariant.BlueBuyButton)
await createFunnel(users.slice(COUNT_BLUE), ExperimentVariant.GreenBuyButton)
}
createFunnel 這邊我們遞迴所有 EventType 的類型,然後 connect 對應類型的 event 到每個 user 中
const createFunnel = async (users: User[], variant: ExperimentVariant) => {
for (const event of [
EventType.PageOpened,
EventType.ProductPutInShoppingCart,
EventType.AddressFilled,
EventType.AddressFilled,
EventType.CheckedOut,
]) {
await createEvents(users, variant, event)
}
}
createEvents 就是很單純的用 Prisma.validator 當作 input ,然後在 create trackingEvent 的時候 connect 到 userId
const createEvents = async (users: User[], variant: ExperimentVariant, event: EventType) => {
const eventsData = Prisma.validator(
prisma,
'trackingEvent',
'createMany'
)({
data: users.map(({ id }) => ({
userId: id,
variant,
type: event
}))
})
await prisma.trackingEvent.createMany(eventsData)
}
最後我們在 createFunnel 中加上 createPosts 的邏輯,讓每個 user 可以有不同數量的 posts
const createFunnel = async (users: User[], variant: ExperimentVariant) => {
//..
const randomPostCounts = faker.helpers.rangeToNumber({ min: 0, max: 5 })
for (let i = 0; i < randomPostCounts; i++) {
await createPosts(users)
}
}
createPosts 也是很單純的用 Prisma.validator 然後 create post 後 connect 到 userId
const createPosts = async (users: User[]) => {
const postData = Prisma.validator(
prisma,
'post',
'createMany'
)({
data: users.map(({ id }) => ({
content: faker.lorem.word(20),
title: faker.lorem.word(5),
authorId: id,
}))
})
await prisma.post.createMany(postData)
}
最後別忘記在 package.json 加上 prisma 的 tag
//package.json
"prisma": {
"seed": "tsx prisma/seed.ts"
}
之後執行 seed data 到我們 DB
>npx prisma db seed
然後到 prisma studio 看一下有沒有資料,這樣我們就完成 seed data 的內容了
不知道大家還記不記得,prisma 除了有 prisma client 的 API 可以執行 CRUD 的 DB query 外,還可以執行 raw SQL 的部分,在 prisma client 有一個 $queryRaw 可以直接執行,那這邊我們就簡單查詢一下 Post 的資料
as 當作 model 的 alias
p.id as "postId" 則是將 table 中對應的欄位轉成在 return data 鍾顯看到的名稱是什麼JOIN 將 Post 跟 User 的 table 綁在一起如此就可以看到 post 跟 user 的資料createAt DESC 則是預設排序const prisma = new PrismaClient()
const main = async () => {
const result = await prisma.$queryRaw`Select
p.id as "postId",
p.title as "postTitle",
p.content as "postContent",
p.published
FROM "Post" as p
JOIN "User" as u on p."authorId" = u.id
ORDER By p."createAt" DESC
`
console.log(result)
}
最後執行 index.ts 看結果
>tsx --watch index.ts
如果我們就成功用 prisma 執行 raw SQL 拉~
[
//..
{
postId: 2641,
postTitle: 'atqui',
postContent: 'tamquam',
published: false
},
//..
]
但 raw SQL 的寫法有一個問題就是沒有 type safe ,所以我們需要對照 SQL 的 table 確定欄位有沒有錯誤等等,這樣就變成每次要執行 $queryRaw 都要重新寫一次,這樣也很難 maintain 所以這時候 TypedSQL 就登場拉,他可以幫你把上面的 raw SQl 部分轉成一個 function 之後也方便你重複使用,至於怎麼完成的我們明天再繼續~
✅ 前端社群 :
https://lihi3.cc/kBe0Y